
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/Window.h>

#include <Liberty3D/Engine/Conversion.h>
#include <Liberty3D/Engine/GLException.h>
#include <Liberty3D/Engine/Win32Exception.h>

#include <CommCtrl.h>

#include "resource.h"

using namespace Liberty::Engine;

#ifdef LIBERTY_OGL
#define WGL_CONTEXT_MAJOR_VERSION_ARB  0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB  0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB    0x2093
#define WGL_CONTEXT_FLAGS_ARB          0x2094
#define WGL_CONTEXT_PROFILE_MASK_ARB   0x9126

#define WGL_CONTEXT_DEBUG_BIT_ARB               0x0001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB  0x0002

#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB           0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB  0x00000002

#define GL_ERROR_INVALID_VERSION_ARB   0x2095
#define GL_ERROR_INVALID_PROFILE_ARB   0x2096

typedef void (APIENTRY *glDebugMessageControlProto)(
    GLenum source,
    GLenum type,
    GLenum severity,
    GLsizei count,
    const GLuint* ids,
    GLboolean enabled);

typedef void (APIENTRY *DEBUGPROCARB)(
    GLenum source,
    GLenum type,
    GLuint id,
    GLenum severity,
    GLsizei length,
    const char* message,
    GLvoid* userParam);

typedef void (APIENTRY* glDebugMessageCallbackProto)(
    DEBUGPROCARB callback,
    void* userParam);

typedef HGLRC (APIENTRY* wglCreateContextAttribsProto)(
    HDC        hDC,
    HGLRC      hShareContext,
    const int* attribList);
#endif

Window::Window() : _hWnd(NULL), _fullScreen(false), _bps(32) {
}

Window::~Window() {
}

void Window::init(HINSTANCE hInst) {
    INITCOMMONCONTROLSEX cc;

    memset(&cc, 0, sizeof(cc));
    cc.dwSize = sizeof(cc);
    InitCommonControlsEx(&cc);

    WNDCLASSEX wc; // Windows Class Structure

    hInstance = hInst;

    memset(&wc, 0, sizeof(wc));
    wc.cbSize           = sizeof(wc);
    wc.style            = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;   // Redraw On Size, And Own DC For Window.
    wc.cbClsExtra       = 0;                                    // No Extra Window Data
    wc.cbWndExtra       = sizeof(Window*);
    wc.hInstance        = hInstance;                            // Set The Instance
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);          // Load The Arrow Pointer
    wc.lpszClassName    = L"Liberty3D";                         // Set The Class Name
    wc.lpfnWndProc      = (WNDPROC)Window::Proc;                // WndProc Handles Messages

    // Attempt To Register The Window Class
    if (!RegisterClassEx(&wc)) {
        throw Win32Exception(GetLastError(), IDS_ERROR_CANT_REGISTER_CLASS);
    }
}

void Window::windowStyles(DWORD& dwStyle, DWORD& dwExStyle) {
    if (_fullScreen) {
        dwExStyle = WS_EX_APPWINDOW;  // Window Extended Style
        dwStyle   = WS_POPUP;         // Windows Style
    }
    else {
        dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
        dwStyle   = WS_OVERLAPPEDWINDOW;                // Windows Style
    }

    dwStyle |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
}

void Window::ensureCreated(bool recreate) {
    std::wstring title;

    if (recreate) {
        if (_hWnd) {
            title.resize(GetWindowTextLength(_hWnd) + 1);
            GetWindowText(_hWnd, (LPWSTR)title.data(), (int)title.size());
            DestroyWindow(_hWnd);
            _hWnd = NULL;
        }
    }

    if (_hWnd == NULL) {
        DWORD dwExStyle; // Window Extended Style
        DWORD dwStyle;   // Window Style

        windowStyles(dwStyle, dwExStyle);
        ShowCursor(!_fullScreen);

        std::shared_ptr<Window>* userData = new std::shared_ptr<Window>(shared_from_this());

        if (CreateWindowEx(
            dwExStyle,                                             // Extended Window Style
            L"Liberty3D",                                          // Class Name
            title.c_str(),                                         // Window Title
            dwStyle,                                               // Window Style
            0, 0,                                                  // Window Position
            _windowRect.right  - _windowRect.left,                 // Calculate Window Width
            _windowRect.bottom - _windowRect.top,                  // Calculate Window Height
            NULL,                                                  // No Parent Window
            NULL,                                                  // No Menu
            hInstance,                                             // Instance
            userData) == NULL) {
                DWORD dwLastError = GetLastError();

                delete userData;
                throw Win32Exception(dwLastError, IDS_ERROR_CANT_CREATE_WINDOW);
        }

        ensureContextCreated(recreate);
    }
}

void Window::ensureContextCreated(bool recreate) {
#if defined(LIBERTY_OGL)
    PIXELFORMATDESCRIPTOR pfd = { 
        sizeof(PIXELFORMATDESCRIPTOR),  //  size of this pfd  
        1,                     // version number  
        PFD_DRAW_TO_WINDOW |   // support window  
        PFD_SUPPORT_OPENGL |   // support OpenGL  
        PFD_DOUBLEBUFFER,      // double buffered  
        PFD_TYPE_RGBA,         // RGBA type  
        0,                     // color depth  
        0, 0, 0, 0, 0, 0,      // color bits ignored  
        0,                     // no alpha buffer  
        0,                     // shift bit ignored  
        0,                     // no accumulation buffer  
        0, 0, 0, 0,            // accum bits ignored  
        0,                     // z-buffer      
        0,                     // no stencil buffer  
        0,                     // no auxiliary buffer  
        PFD_MAIN_PLANE,        // main layer  
        0,                     // reserved  
        0, 0, 0                // layer masks ignored  
    };

    switch (_bps)
    {
    case 16:
        pfd.cColorBits = 16;
        pfd.cDepthBits = 16;
        break;

    case 32:
    default:
        pfd.cColorBits = 24;
        pfd.cDepthBits = 32;
        break;
    }

    GLenum __gl_err;

    _hDC          = GetDC(_hWnd);
    _pixelFormat = ChoosePixelFormat(_hDC, &pfd);

    SetPixelFormat(_hDC, _pixelFormat, &pfd);
    _context = wglCreateContext(_hDC);

    LY_CHECKED_GL_CALL(wglMakeCurrent, (_hDC, _context));

    wglCreateContextAttribsProto wglCreateContextAttribs = (wglCreateContextAttribsProto)wglGetProcAddress("wglCreateContextAttribsARB");

    if (wglCreateContextAttribs) {
        wglMakeCurrent  (_hDC, NULL);
        wglDeleteContext(_context);
        _context = NULL;

        int arbFlags = WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;

#ifdef _DEBUG
        arbFlags |= WGL_CONTEXT_DEBUG_BIT_ARB;
#endif

        int attribList[] = {
            WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
            WGL_CONTEXT_MINOR_VERSION_ARB, 3,
            WGL_CONTEXT_FLAGS_ARB,         arbFlags,
            WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
            0,                             0
        };

        _context = wglCreateContextAttribs(_hDC, 0, attribList);
        LY_CHECKED_GL_CALL(wglMakeCurrent, (_hDC, _context));
    }
#endif
}

void Window::show() {
    show(SW_SHOW);
}

void Window::show(UINT command) {
    ensureCreated();
    ShowWindow(_hWnd, command);
}

void Window::hide() {
    show(SW_HIDE);
}

void Window::setTitle(const std::string& title) {
    ensureCreated();
    SetWindowText(_hWnd, UTF8_To_UTF16(title.c_str()).c_str());
}

LRESULT Window::handleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) {
    return 0;
}

LRESULT CALLBACK Window::Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    LONG_PTR userData = GetWindowLongPtr(hWnd, GWLP_USERDATA);
    
    if (userData == NULL) {
        if (message == WM_CREATE) {
            LPCREATESTRUCT pCreateStruct = (LPCREATESTRUCT)lParam;

            userData = (LONG_PTR)pCreateStruct->lpCreateParams;
            (*((std::shared_ptr<Window>*)userData))->_hWnd = hWnd;
            SetWindowLongPtr(hWnd, GWLP_USERDATA, userData);
        }
        else {
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    else if (message == WM_NCDESTROY) {
        (*((std::shared_ptr<Window>*)userData))->_hWnd = NULL;

        delete ((std::shared_ptr<Window>*)userData);
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    bool    handled = false;
    LRESULT result;

    result = (*((std::shared_ptr<Window>*)userData))->handleMessage(hWnd, message, wParam, lParam, handled);

    if (!handled) {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return result;
}
